#include <ds/ring_buffer.h>
#include <lib/math.h>
#include <string.h>

void rbuffer_init(rbuffer_t* buf, void* buffer, size_t size, u32 flags) {
    *buf = (rbuffer_t){
        .data = buffer,
        .size = size,
        .pread = 0,
        .pwrite = 0,
        .free_len = size,
        .flags = flags
    };
    mutex_init(&buf->lock);
}

size_t rbuffer_write(rbuffer_t* fbuf, void* data, size_t count) {
    size_t write_count = 0, pos = fbuf->pwrite;

    mutex_lock(&fbuf->lock);

    if (!fbuf->free_len) {
        mutex_unlock(&fbuf->lock);
        return 0;
    }

    if (pos >= fbuf->pread) {
        size_t size_to_end = MIN(fbuf->size - pos, count);
        size_t size_from_start = MIN(fbuf->pread, count - size_to_end);
        memcpy(fbuf->data + pos, data, size_to_end);
        memcpy(fbuf->data, data + size_to_end, size_from_start);

        write_count = size_from_start + size_to_end;
    } else {
        write_count = MIN(fbuf->pread - pos, count);
        memcpy(fbuf->data + pos, data, write_count);
    }

    fbuf->pwrite = (fbuf->pwrite + write_count) % fbuf->size;
    fbuf->free_len -= write_count;

    mutex_unlock(&fbuf->lock);
    return write_count;
}

size_t rbuffer_read(rbuffer_t* fbuf, void* buf, size_t count) {
    size_t read_count = 0, pos = fbuf->pread;

    mutex_lock(&fbuf->lock);

    if (fbuf->free_len == fbuf->size) {
        mutex_unlock(&fbuf->lock);
        return 0;
    }

    if (pos >= fbuf->pwrite) {
        size_t size_to_end = MIN(fbuf->size - pos, count);
        size_t size_from_start = MIN(fbuf->pwrite, count - size_to_end);
        memcpy(buf, fbuf->data + pos, size_to_end);
        memcpy(buf + size_to_end, fbuf->data, size_from_start);

        read_count = size_from_start + size_to_end;
    } else {
        read_count = MIN(fbuf->pwrite - pos, count);
        memcpy(buf, fbuf->data + pos, read_count);
    }

    fbuf->pread = (fbuf->pread + read_count) % fbuf->size;
    fbuf->free_len += read_count;

    mutex_unlock(&fbuf->lock);
    return read_count;
}

size_t rbuffer_readone_async(rbuffer_t* fbuf, char* data) {
    if (fbuf->free_len == fbuf->size) {
        return 0;
    }

    char* buf = fbuf->data;
    *data = buf[fbuf->pread];
    fbuf->pread = (fbuf->pread + 1) % fbuf->size;
    fbuf->free_len++;
    return 1;
}

void rbuffer_set_wptr(rbuffer_t* fbuf, size_t wptr) {
    fbuf->pwrite = wptr;
    if (wptr > fbuf->pread) {
        fbuf->free_len = fbuf->size - wptr + fbuf->pread;
    } else {
        fbuf->free_len = fbuf->pread - wptr;
    }
}


void rbuffer_set_rptr(rbuffer_t* fbuf, size_t rptr) {
    fbuf->pread = rptr;
    if (rptr > fbuf->pwrite) {
        fbuf->free_len = rptr - fbuf->pwrite;
    } else {
        fbuf->free_len = fbuf->size - fbuf->pwrite + rptr;
    }
}

void rbuffer_clear(rbuffer_t* fbuf) {
    mutex_lock(&fbuf->lock);
    fbuf->pread = 0;
    fbuf->pwrite = 0;
    fbuf->free_len = fbuf->size;
    mutex_unlock(&fbuf->lock);
}